package com.kefu.chat.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.tree.TreeNode;
import cn.hutool.core.lang.tree.TreeUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.dfa.WordTree;
import cn.hutool.http.HtmlUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.kefu.basic.mapper.BasicTagMapper;
import com.kefu.basic.model.BasicTag;
import com.kefu.basic.service.BasicShareService;
import com.kefu.basic.util.RedisLockUtil;
import com.kefu.chat.mapper.ChatAdminStatusMapper;
import com.kefu.chat.mapper.ChatSessionMapper;
import com.kefu.chat.mapper.ChatSessionTagRelMapper;
import com.kefu.chat.model.*;
import com.kefu.chat.util.ChatConstant;
import com.kefu.chat.util.RongCloudUtils;
import com.kefu.chat.vo.*;
import com.kefu.chat.vo.ChatAdminParam.*;
import com.kefu.chat.vo.ChatEventMsg.*;
import com.kefu.common.context.KefuContext;
import com.kefu.common.mybatis.Order;
import com.kefu.common.util.Assert;
import com.kefu.common.util.JacksonUtil;
import com.kefu.common.util.StreamUtil;
import com.kefu.crm.mapper.CrmCustomerMapper;
import com.kefu.crm.model.CrmCustomer;
import com.kefu.framework.config.KefuProperties;
import com.kefu.sys.mapper.SysGroupMapper;
import com.kefu.sys.mapper.SysTenantMapper;
import com.kefu.sys.mapper.SysUserMapper;
import com.kefu.sys.model.SysGroup;
import com.kefu.sys.model.SysUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
public class ChatAdminService {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    @Autowired
    private ChatCacheService chatCacheService;
    @Autowired
    private ChatSessionService chatSessionService;
    @Autowired
    private ChatSessionMsgService chatSessionMsgService;
    @Autowired
    private ChatUserService chatUserService;
    @Resource
    private SysUserMapper sysUserMapper;
    @Resource
    private BasicTagMapper basicTagMapper;
    @Resource
    private ChatSessionTagRelMapper chatSessionTagRelMapper;
    @Resource
    private ChatSessionMapper chatSessionMapper;
    @Resource
    private SysGroupMapper sysGroupMapper;
    @Autowired
    private ChatReplyService chatReplyService;
    @Resource
    private ChatAdminStatusMapper chatAdminStatusMapper;
    @Autowired
    private ChatWxService chatWxService;
    @Autowired
    private ChatWxMiniService chatWxMiniService;
    @Autowired
    private ChatWxWorkService chatWxWorkService;
    @Resource
    private SysTenantMapper sysTenantMapper;
    @Autowired
    BasicShareService basicShareService;
    @Autowired
    RedisLockUtil redisLockUtil;
    @Autowired
    KefuProperties kefuProperties;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Resource
    private CrmCustomerMapper crmCustomerMapper;
    @Autowired
    private ChatReceptConfigService chatReceptConfigService;


    public ChatAdmin info() {
        String adminId = KefuContext.getKefuContext().getUserId();
        return chatCacheService.getAdmin(adminId);
    }

    public ChatAdmin change(AdminStatusChangeP change) {
        ChatAdmin admin;
        Integer status = change.getStatus();
        switch (status) {
            case 1: admin = online();
                break;
            case 0: admin = out(KefuContext.getUid(), ChatConstant.out_type_close);
                break;
            default: admin = busy(status);
                break;
        }
        logger.info("admin_status_change:{}, {}", status, JacksonUtil.toString(admin));
        return admin;
    }

    public ChatAdmin online() {
        String adminId = KefuContext.getKefuContext().getUserId();
        String tenantId = KefuContext.getKefuContext().getTenantId();
        ChatAdmin admin = chatCacheService.getAdmin(adminId);
        logger.info("admin_online:{}", admin);
        long time = System.currentTimeMillis();
        boolean saveStatus = false;
        ChatAdminStatus status = new ChatAdminStatus();
        //离线状态上线，从数据库查询到缓存，加入企业客服set集合
        if (admin == null || admin.getStatus() == ChatConstant.admin_status_offline) {
            admin = loadAdminFromDB(adminId);
            admin.setStatus(ChatConstant.admin_status_online);
            admin.setStatusTime(time);
            admin.setAdminFace(KefuContext.getKefuContext().getUserFace());
            chatCacheService.setAdmin(admin);
            chatCacheService.addAdminSet(admin.getTenantId(), adminId);
            awakeWaitList(admin);
            Long lastStatusId = chatAdminStatusMapper.getMaxId(tenantId, adminId);
            if (lastStatusId != null) {
                ChatAdminStatus chatAdminStatus = chatAdminStatusMapper.selectByPrimaryKey(lastStatusId);
                if (Objects.isNull(chatAdminStatus.getUpdateTime())) {
                    long statusDuration = (time - chatAdminStatus.getStatusTime()) / 1000;
                    chatAdminStatus.setId(lastStatusId);
                    chatAdminStatus.setStatusDuration(statusDuration);
                    chatAdminStatus.setUpdateTime(time);
                    chatAdminStatusMapper.updateById(chatAdminStatus);
                }
            }
            saveStatus = true;
        } else if (admin.getStatus() == ChatConstant.admin_status_busy) {
            long statusTime = admin.getStatusTime();
            admin.setStatus(ChatConstant.admin_status_online);
            admin.setAdminFace(KefuContext.getKefuContext().getUserFace());
            admin.setStatusTime(time);
            chatCacheService.setAdmin(admin);
            Long lastStatusId = chatAdminStatusMapper.getMaxId(tenantId, adminId);
            if (statusTime != 0 && lastStatusId != null) {
                long statusDuration = (time - statusTime) / 1000;
                ChatAdminStatus lastStatus = new ChatAdminStatus();
                lastStatus.setId(lastStatusId);
                lastStatus.setStatusDuration(statusDuration);
                lastStatus.setUpdateTime(time);
                chatAdminStatusMapper.updateById(lastStatus);
            }
            awakeWaitList(admin);
            saveStatus = true;
        }
        if (saveStatus) {
            status.setTenantId(tenantId);
            status.setAdminId(adminId);
            status.setStatusType(ChatConstant.admin_status_online);
            status.setStatusTime(time);
            chatAdminStatusMapper.insert(status);
        }
        return admin;
    }

    public ChatAdmin busy(int statusType) {
        String adminId = KefuContext.getUid();
        String tenantId = KefuContext.getTid();
        ChatAdmin admin = chatCacheService.getAdmin(adminId);
        long time = System.currentTimeMillis();
        //离线状态置忙，从数据库查询到缓存，加入企业客服set集合
        boolean flag = false;
        Long maxId = chatAdminStatusMapper.getMaxId(tenantId, adminId);
        long statusTime = 0;
        if (admin != null) {
            statusTime = admin.getStatusTime();
        } else {
            admin = loadAdminFromDB(adminId);
            if (Objects.nonNull(maxId)) {
                ChatAdminStatus chatAdminStatus = chatAdminStatusMapper.selectByPrimaryKey(maxId);
                if (Objects.nonNull(chatAdminStatus.getUpdateTime())) {
                    statusTime = chatAdminStatus.getStatusTime();
                }
            }
            flag = true;
        }
        if (Objects.isNull(maxId)) {
            flag = true;
        } else {
            ChatAdminStatus chatAdminStatus = chatAdminStatusMapper.selectByPrimaryKey(maxId);
            if (statusTime != 0) {
                if (chatAdminStatus.getStatusType() == statusType) {
                    time = statusTime;
                } else {
                    long statusDuration = (time - statusTime) / 1000;
                    chatAdminStatus.setId(maxId);
                    chatAdminStatus.setStatusDuration(statusDuration);
                    chatAdminStatus.setUpdateTime(time);
                    chatAdminStatusMapper.updateById(chatAdminStatus);
                    flag = true;
                }
            }
        }
        if (flag) {
            ChatAdminStatus status = new ChatAdminStatus();
            status.setTenantId(tenantId);
            status.setAdminId(adminId);
            status.setStatusType(statusType);
            status.setStatusTime(time);
            chatAdminStatusMapper.insert(status);
        }
        admin.setStatus(ChatConstant.admin_status_busy);
        admin.setStatusTime(time);
        chatCacheService.setAdmin(admin);
        chatCacheService.addAdminSet(tenantId, adminId);
        logger.info("admin_busy:{},{}", admin, flag);
        return admin;
    }

    public ChatAdmin out(String adminId, int outType) {
        ChatAdmin admin = chatCacheService.getAdmin(adminId);
        logger.info("admin_out:{},{},{}", adminId, outType, admin);
        if (admin == null) {
            SysUser sysUser = sysUserMapper.selectById(adminId);
            chatCacheService.delAdminPollTime(sysUser.getTenantId(), adminId);
            return null;
        }
        String tenantId = admin.getTenantId();
        chatCacheService.delAdminPollTime(tenantId, adminId);
        long time = System.currentTimeMillis();
        if (admin.getStatus() != ChatConstant.admin_status_offline) {
            long statusTime = admin.getStatusTime();
            admin.setStatus(ChatConstant.admin_status_offline);
            admin.setStatusTime(time);
            chatCacheService.setAdmin(admin);
            chatCacheService.delAdminSet(tenantId, adminId);
            Set<String> adminUsers = chatCacheService.getAdminUserSet(adminId);
            long allAdminSize = chatCacheService.getAdminSetSize(tenantId);
            if (allAdminSize < 1) {
                List<String> waitUsers = chatCacheService.getWaitList(tenantId);
                adminUsers.addAll(waitUsers);
                chatCacheService.emptyWaitList(tenantId);
            }
            for (String userId : adminUsers) {
                chatUserService.out(userId, ChatConstant.out_type_remove);
            }
            chatCacheService.emptyAdminUserSet(adminId);
            Long lastStatusId = chatAdminStatusMapper.getMaxId(tenantId, adminId);
            if (statusTime != 0 && lastStatusId != null) {
                long statusDuration = (time - statusTime) / 1000;
                ChatAdminStatus lastStatus = new ChatAdminStatus();
                lastStatus.setId(lastStatusId);
                lastStatus.setStatusDuration(statusDuration);
                lastStatus.setUpdateTime(time);
                chatAdminStatusMapper.updateById(lastStatus);
            }
            ChatAdminStatus status = new ChatAdminStatus();
            status.setTenantId(tenantId);
            status.setAdminId(adminId);
            status.setStatusType(ChatConstant.admin_status_offline);
            status.setStatusTime(time);
            chatAdminStatusMapper.insert(status);
        }
        return admin;
    }

    public void removeUser(AdminUserP param) {
        String adminId = KefuContext.getKefuContext().getUserId();
        ChatAdmin admin = chatCacheService.getAdmin(adminId);
        logger.info("admin_removeUser:{},{}", param, admin);
        chatUserService.out(param.getUserId(), ChatConstant.out_type_remove);
        chatCacheService.delAdminUserSet(adminId, param.getUserId());
        awakeWaitList(admin);
    }

    public AdminSendMsgR sendMsg(AdminSendMsgP param) {
        AdminSendMsgR result = new AdminSendMsgR();
        String adminId = KefuContext.getKefuContext().getUserId();
        ChatAdmin admin = chatCacheService.getAdmin(adminId);
        ChatUser user = chatCacheService.getUser(param.getUserId());
        logger.info("admin_sendMsg:{},{},{}", param, admin, user);
        if (user == null) {
            return result;
        }
        String sessionId = user.getSessionId();
        String msgId = IdUtil.simpleUUID();
        long msgTime = System.currentTimeMillis();
        // 校验是否触发敏感词
        Set<String> wordList = basicShareService.getSensitiveWord(user.getTenantId() ,1);
        WordTree wordTree = new WordTree();
        wordTree.addWords(wordList);
        int sensitiveFlag = 0;
        String sensitiveWords = null;
        Integer readFlag = null;
        List<String> sensitiveList = wordTree.matchAll(param.getMsgContent(), -1, false, true);
        if (CollUtil.isNotEmpty(sensitiveList)) {
            sensitiveFlag = 1;
            result.setSensitiveWords(sensitiveList);
            sensitiveWords = String.join(",", sensitiveList);
        } else {
            UserGetMsgE userGetMsgE = new UserGetMsgE();
            userGetMsgE.setMsgId(msgId);
            userGetMsgE.setAdminNick(admin.getAdminNick());
            userGetMsgE.setMsgType(param.getMsgType());
            userGetMsgE.setMsgContent(param.getMsgContent());
            userGetMsgE.setFileName(param.getFileName());
            userGetMsgE.setMsgTime(msgTime);
            userGetMsgE.setSensitiveFlag(sensitiveFlag);
            if (user.getStatus() == ChatConstant.user_status_online) {
                if (user.getChannelType() == ChatConstant.channel_type_wx) {
                    chatWxService.sendMsg(admin, user, param.getMsgType(), param.getMsgContent());
                } else if (user.getChannelType() == ChatConstant.channel_type_wx_mini) {
                    chatWxMiniService.sendMsg(admin, user, param.getMsgType(), param.getMsgContent());
                } else if (user.getChannelType() == ChatConstant.channel_type_wx_work) {
                    chatWxWorkService.sendMsg(admin, user, param.getMsgType(), param.getMsgContent());
                } else {
                    chatCacheService.sendEventMsg(user.getPollId(), userGetMsgE);
                }
            }
            if (param.getFrom() == 2) {
                sessionId = param.getSessionId();
                //存储离线消息
                chatCacheService.sendOfflineMsg(user.getUserId(), userGetMsgE);
            }
            //消息已读未读
            ChatReceptConfig chatReceptConfig = chatReceptConfigService.getConfig(adminId);
            if (Objects.equals(chatReceptConfig.getReadFlag(), 1)) {
                if(user.getChannelType()==ChatConstant.channel_type_pc || user.getChannelType()==ChatConstant.channel_type_h5
                        || user.getChannelType()==ChatConstant.channel_type_app) {
                    readFlag = 0;
                }else {
                    readFlag = 1;
                }
            }
        }
        ChatSessionMsg msgLog = ChatSessionMsg.builder()
                .id(msgId)
                .userId(user.getUserId())
                .sessionId(sessionId)
                .tenantId(user.getTenantId())
                .sender(adminId)
                .senderName(admin.getAdminNick())
                .senderType(ChatConstant.msg_sender_admin)
                .receiveType(ChatConstant.msg_sender_user)
                .receive(user.getUserId())
                .msgType(param.getMsgType())
                .msgContent(param.getMsgContent())
                .fileName(param.getFileName())
                .audioDuration(param.getAudioDuration())
                .msgTime(msgTime).adminId(adminId)
                .sensitiveFlag(sensitiveFlag)
                .sensitiveWords(sensitiveWords)
                .readFlag(readFlag)
                .clientType(ChatConstant.msg_client_all_type)
                .build();
        chatSessionMsgService.insertSessionMsg(msgLog);
        result.setMsgId(msgId);
        result.setMsgTime(msgTime);
        result.setReadFlag(readFlag);
        return result;
    }

    public AdminGetEventMsgR getEventMsg(AdminGetEventMsgP param) {
        AdminGetEventMsgR result = new AdminGetEventMsgR();
        String adminId = KefuContext.getKefuContext().getUserId();
        ChatAdmin admin = chatCacheService.getAdmin(adminId);
        result.setMsgList(chatCacheService.getEventMsg(param.getPollId()));
        result.setWaitSize(chatCacheService.getWaitSize(admin.getTenantId()));
        result.setStatus(admin.getStatus());
        if (admin.getStatus() != ChatConstant.admin_status_offline) {
            chatCacheService.setAdminPollTime(admin.getTenantId(), adminId);
        }
        if (CollUtil.isNotEmpty(result.getMsgList())) {
            logger.info("admin_getEventMsg:{},{}", admin, result);
        }
        return result;
    }

    public void transferUser(AdminTransferUserP param) {
        String adminId = KefuContext.getKefuContext().getUserId();
        chatCacheService.delAdminUserSet(adminId, param.getUserId());
        ChatUser user = chatCacheService.getUser(param.getUserId());
        ChatAdmin toAdmin = chatCacheService.getAdmin(param.getToAdminId());
        logger.info("transferUser:{},{},{},{}", param, adminId, user, toAdmin);
        chatSessionService.chatConnect(user, toAdmin, ChatConstant.connect_type_transfer);
        ChatData chatData = chatCacheService.getData(user.getSessionId());
        if (chatData != null) {
            chatData.getReplyDuration().clear();
            chatCacheService.setData(chatData);
        }
    }

     
    public void grabUser(AdminUserP param) {
        String adminId = KefuContext.getKefuContext().getUserId();
        ChatAdmin admin = chatCacheService.getAdmin(adminId);
        ChatUser user = chatCacheService.getUser(param.getUserId());
        logger.info("admin_grabUser:{},{},{}", param, admin, user);
        if (admin == null || admin.getStatus() == ChatConstant.admin_status_offline || adminId.equals(user.getAdminId())) {
        	logger.warn("客户已经属于当前用户，不需要抢话");
            return;
        }
        ChatAdmin oldAdmin = chatCacheService.getAdmin(user.getAdminId());
        chatCacheService.delAdminUserSet(oldAdmin.getAdminId(), param.getUserId());
        AdminUserGrab adminMsg = BeanUtil.toBean(user, AdminUserGrab.class);
        chatCacheService.sendEventMsg(oldAdmin.getPollId(), adminMsg); //给原来客服发送被抢接消息
        chatSessionService.chatConnect(user, admin, ChatConstant.connect_type_grab);
        ChatData chatData = chatCacheService.getData(user.getSessionId());
        if (chatData != null) {
            chatData.getReplyDuration().clear();
            chatCacheService.setData(chatData);
        }
    }

     
    public ChatReceptConfig getReceptConfig() {
        String adminId = KefuContext.getKefuContext().getUserId();
        ChatReceptConfig chatReceptConfig = chatReceptConfigService.getConfig(adminId);
        if (chatReceptConfig == null) {
            //查询不到，生成一条新数据
            chatReceptConfig = new ChatReceptConfig();
            chatReceptConfig.setAdminId(adminId);
            chatReceptConfig.setId(IdUtil.simpleUUID());
            chatReceptConfig.setTenantId(KefuContext.getKefuContext().getTenantId());
            try {
                chatReceptConfigService.save(chatReceptConfig);
                chatReceptConfig = chatReceptConfigService.findById(chatReceptConfig.getId());
            } catch (Exception e) {
                e.printStackTrace();//adminId 唯一索引，避免并发重复插入，用异常捕获
            }
        }
        return chatReceptConfig;
    }

     
    public void updateReceptConfig(ChatReceptConfig param) {
        chatReceptConfigService.update(param);
    }

     
    public void updateWoCreateMode(AdminUserP param) {
        ChatData chatData = chatCacheService.getData(param.getSessionId());
        if (chatData != null) {
            chatData.setWoCreateMode(1);
            chatCacheService.setData(chatData);
        }
        chatSessionService.update(Wrappers.lambdaUpdate(ChatSession.class).set(ChatSession::getWoCreateMode, 1).eq(ChatSession::getSessionId, param.getSessionId()));
    }

    public void inviteUser(AdminUserP param) {
        String adminId = KefuContext.getKefuContext().getUserId();
        ChatAdmin admin = chatCacheService.getAdmin(adminId);
        ChatUser user = chatCacheService.getUser(param.getUserId());
        logger.info("admin_inviteUser:{},{},{}", param, admin, user);
        chatSessionService.chatConnect(user, admin, ChatConstant.connect_type_invite);
    }
    public List<AdminOnlineUserR> getOnlineUser() {
        List<AdminOnlineUserR> result = new ArrayList<>();
        String adminId = KefuContext.getKefuContext().getUserId();
        Set<String> userIds = chatCacheService.getAdminUserSet(adminId);
        for (String userId : userIds) {
            ChatUser user = chatCacheService.getUser(userId);
            AdminOnlineUserR onlineUser = BeanUtil.toBean(user, AdminOnlineUserR.class);
            ChatData chatData = chatCacheService.getData(user.getSessionId());
            if (chatData != null) {
                onlineUser.setAdminMsgCount(chatData.getAdminMsgCount());
                onlineUser.setLastMsgTime(chatData.getLastMsgTime());
                onlineUser.setLastMsgContent(chatData.getLastMsgContent());
                onlineUser.setLastMsgType(chatData.getLastMsgType());
                onlineUser.setLastMsgSender(chatData.getSender());
                onlineUser.setLastMsgSenderType(chatData.getSenderType());
                onlineUser.setLastMsgSenderName(chatData.getSenderName());
                if (chatData.getUserEvaluate() != null && chatData.getUserEvaluate().getEvaluateAdminMode() != null) {
                    onlineUser.setEvaluateAdminMode(chatData.getUserEvaluate().getEvaluateAdminMode());
                }
                onlineUser.setAdminTimeout(chatData.getAdminTimeout());
                List<ChatSessionTagRel> chatSessionTagRels = chatSessionTagRelMapper.selectList(Wrappers.lambdaQuery(ChatSessionTagRel.class)
                        .eq(ChatSessionTagRel::getSessionId, chatData.getSessionId()));
                if (CollUtil.isNotEmpty(chatSessionTagRels)) {
                    List<BasicTag> basicTags = basicTagMapper.selectList(Wrappers.lambdaQuery(BasicTag.class)
                            .in(BasicTag::getTagId, chatSessionTagRels.stream().map(ChatSessionTagRel::getTagId).collect(Collectors.toList())));
                    onlineUser.setBasicTags(basicTags);
                }
            }
            result.add(onlineUser);
        }
        logger.info("admin_getOnlineUser:{},{}", KefuContext.getKefuContext(), result);
        return result;
    }

    public List<AdminWaitUserR> getWaitUser() {
        List<AdminWaitUserR> result = new ArrayList<>();
        String tenantId = KefuContext.getKefuContext().getTenantId();
        List<String> userIds = chatCacheService.getWaitList(tenantId);
        for (String userId : userIds) {
            ChatUser chatUser = chatCacheService.getUser(userId);
            AdminWaitUserR user = BeanUtil.toBean(chatUser, AdminWaitUserR.class);
            String groupName = user.getGroupName();
            if (StrUtil.isBlank(groupName)) {
                String groupId = user.getGroupId();
                if (StrUtil.isNotBlank(groupId)) {
                    SysGroup sysGroup = sysGroupMapper.selectByPrimaryKey(groupId);
                    if (Objects.nonNull(sysGroup)) {
                        user.setGroupName(sysGroup.getGroupName());
                    }
                }
            }
            user.setWaitDuration((System.currentTimeMillis() - user.getWaitTime()) / 1000);
            result.add(user);
        }
        logger.info("admin_getWaitUser:{},{}", KefuContext.getKefuContext(), result);
        return result;
    }

    public List<ChatAdmin> getOtherAdmin() {
        String tenantId = KefuContext.getKefuContext().getTenantId();
        String adminId = KefuContext.getKefuContext().getUserId();
        Set<String> adminIds = chatCacheService.getAdminSet(tenantId);
        List<ChatAdmin> adminList = new ArrayList<>();
        for (String id : adminIds) {
            if (!id.equals(adminId)) {
                ChatAdmin admin = chatCacheService.getAdmin(id);
                if (admin != null) {
                    admin.setCurrentAccept(chatCacheService.getAdminUserSetSize(id));
                    adminList.add(admin);
                }
            }
        }
        logger.info("admin_getOtherAdmin:{},{}", KefuContext.getKefuContext(), adminList);
        return adminList;
    }

    public ChatUser getUserInfo(AdminUserP param) {
        ChatUser user = chatCacheService.getUser(param.getUserId());
        logger.info("admin_getUserInfo:{},{}", KefuContext.getKefuContext(), user);
        if (user == null || !user.getSessionId().equals(param.getSessionId())) {
            ChatSession session = chatSessionService.findById(param.getSessionId());
            if (session != null && session.getUserId().equals(param.getUserId())) {
                user = BeanUtil.toBean(session, ChatUser.class);
            }
        }
        return user;
    }

    public void updateUserInfo(AdminUpdateUserP param) {
        ChatUser user = chatCacheService.getUser(param.getUserId());
        logger.info("admin_updateUserInfo:{},{},{}", KefuContext.getKefuContext(), param, user);
        if (user != null) {
            user.setCustomerId(param.getCustomerId());
            user.setUserName(param.getUserName());
            chatCacheService.setUser(user);
        }
        if (user == null || user.getStatus() == ChatConstant.user_status_offline || !user.getSessionId().equals(param.getSessionId())) {
            ChatSession chatSession = BeanUtil.toBean(param, ChatSession.class);
            chatSessionService.update(chatSession);
        }
        if (StrUtil.isNotBlank(param.getCustomerId())) {
            CrmCustomer crmCustomer = crmCustomerMapper.selectByPrimaryKey(param.getCustomerId());
            if (Objects.nonNull(crmCustomer)) {
                crmCustomer.setName(param.getUserName());
                crmCustomerMapper.updateById(crmCustomer);
                logger.info("更新客户中心信息: {}", JacksonUtil.toString(crmCustomer));
            }
        }
    }

    public void markUser(AdminMarkUserP param) {
        ChatUser user = chatCacheService.getUser(param.getUserId());
        logger.info("admin_markUser:{},{}", KefuContext.getKefuContext(), user);
        if (user != null && user.getStatus() == ChatConstant.user_status_online && user.getSessionId().equals(param.getSessionId())) {
            user.setMarkMode(param.getMarkMode());
            chatCacheService.setUser(user);
        } else {
            ChatSession chatSession = BeanUtil.toBean(param, ChatSession.class);
            chatSessionService.update(chatSession);
        }
    }

    public CrmCustomer blackUser(AdminBlackUserP param) {
        String userId = param.getUserId();
        ChatUser user = chatCacheService.getUser(userId);
        Assert.isEmpty(user.getCustomerId(), "未保存客户，不能拉黑！");
        logger.info("admin_blackUser:{},{}", KefuContext.getKefuContext(), user);
        user.setBlackMode(param.getBlackMode());
        user.setBlackModeContent(param.getContent());
        chatCacheService.setUser(user);
        chatUserService.out(param.getUserId(), ChatConstant.out_type_black);
        CrmCustomer crmCustomer = crmCustomerMapper.selectByPrimaryKey(user.getCustomerId());
        crmCustomer.setCustomerStatus(0 == param.getBlackMode()  ? 1 : 3);
        crmCustomer.setBlackModeContent(StrUtil.isNotBlank(param.getContent()) ? param.getContent()
                : crmCustomer.getBlackModeContent());
        crmCustomerMapper.updateById(crmCustomer);
        logger.info("admin_blackUser 更新客户信息: {}", JacksonUtil.toString(crmCustomer));
        if (user.getStatus() == ChatConstant.user_status_offline || !user.getSessionId().equals(param.getSessionId())) {
            ChatSession chatSession = BeanUtil.toBean(param, ChatSession.class);
            chatSessionService.update(chatSession);
        }
        return crmCustomer;
    }

    public List<ChatSession> getHistorySession(AdminGetHistorySessionP param) {
        String adminId = KefuContext.getKefuContext().getUserId();
        String userId = param.getUserId();
        ChatSessionSearchVo vo = new ChatSessionSearchVo();
        if (StrUtil.isNotBlank(userId)) {
            vo.setUserId(userId);
        } else {
            vo.setAdminId(adminId);
        }
        vo.setUserName(param.getUserName());
        vo.setMarkMode(param.getMarkMode());
        vo.setMsgContent(param.getMsgContent());
        vo.setSessionStatus(1);
        vo.setTenantId(KefuContext.getTid());
        vo.setOrderField("offline_time");
        vo.setOrder(Order.DESC);
        vo.setPageNo(1);
        vo.setPageSize(Math.min(param.getPageSize(), 20));
        vo.setOfflineTimeStart(param.getOfflineTimeStart());
        vo.setOfflineTimeEnd(param.getOfflineTimeEnd());
        Page<ChatSession> page = new Page<>(vo.getPageNo(), vo.getPageSize(), vo.getPageSize() < 100);
        List<ChatSession> sessionList = chatSessionMapper.getSession(page, vo).getRecords();
        if (CollUtil.isNotEmpty(sessionList)) {
            for (ChatSession session : sessionList) {
                String groupName = session.getGroupName();
                if (StrUtil.isBlank(groupName)) {
                    String groupId = session.getGroupId();
                    if (StrUtil.isNotBlank(groupId)) {
                        SysGroup sysGroup = sysGroupMapper.selectByPrimaryKey(groupId);
                        if (Objects.nonNull(sysGroup)) {
                            session.setGroupName(sysGroup.getGroupName());
                        }
                    }
                }
                List<ChatSessionMsg> lastMsgList = chatSessionMsgService.list(Wrappers.lambdaQuery(ChatSessionMsg.class)
                        .eq(ChatSessionMsg::getSessionId, session.getSessionId())
                        .orderByDesc(ChatSessionMsg::getMsgTime).last("limit 1"));
                if (CollUtil.isNotEmpty(lastMsgList)) {
                    ChatSessionMsg lastMsg = lastMsgList.get(0);
                    session.setLastMsgType(lastMsg.getMsgType());
                    session.setLastMsgContent(lastMsg.getMsgContent());
                    session.setLastMsgSender(lastMsg.getSender());
                    session.setLastMsgSenderType(lastMsg.getSenderType());
                    session.setLastMsgSenderName(lastMsg.getSenderName());
                }
                List<ChatSessionTagRel> chatSessionTagRels = chatSessionTagRelMapper.selectList(Wrappers.lambdaQuery(ChatSessionTagRel.class)
                        .eq(ChatSessionTagRel::getSessionId, session.getSessionId()));
                if (CollUtil.isNotEmpty(chatSessionTagRels)) {
                    List<BasicTag> basicTags = basicTagMapper.selectList(Wrappers.lambdaQuery(BasicTag.class)
                            .in(BasicTag::getTagId, StreamUtil.stream(chatSessionTagRels)
                                    .map(ChatSessionTagRel::getTagId).collect(Collectors.toList())));
                    session.setBasicTag(basicTags);
                }
            }
        }
        return sessionList;
    }

    public void inviteEvaluate(AdminUserP param) {
        UserInvitedEvaluate msg = new UserInvitedEvaluate();
        ChatUser user = chatCacheService.getUser(param.getUserId());
        logger.info("admin_inviteEvaluate:{},{}", KefuContext.getKefuContext(), user);
        chatCacheService.sendEventMsg(user.getPollId(), msg);
    }

    public void typing(AdminUserP param) {
        UserAdminTyping msg = new UserAdminTyping();
        ChatUser user = chatCacheService.getUser(param.getUserId());
        if (user != null && user.getStatus() == ChatConstant.user_status_online) {
            chatCacheService.sendEventMsg(user.getPollId(), msg);
        }
    }

    public void updateSummary(AdminSummary param) {
        logger.info("admin_updateSummary:{},{}", KefuContext.getKefuContext(), param);
        ChatSession chatSession = BeanUtil.toBean(param, ChatSession.class);
        chatSessionService.update(chatSession);
    }

    public List<?> getReplyList(AdminGetReplyListP param) {
        String tenantId = KefuContext.getKefuContext().getTenantId();
        String adminId = KefuContext.getKefuContext().getUserId();
        ChatReply chatReply = new ChatReply();
        chatReply.setTenantId(tenantId);
        chatReply.setType(param.getType());
        if (param.getType() == 1) {
            chatReply.setAdminId(adminId);
        }
        List<ChatReply> replyList = chatReplyService.findByModel(chatReply);
        List<TreeNode<String>> nodeList = CollUtil.newArrayList();
        for (ChatReply reply : replyList) {
            TreeNode<String> node = new TreeNode<>(reply.getId(), reply.getParentId(), reply.getName(), reply.getWeight());
            Map<String, Object> map = new HashMap<>();
            map.put("keyword", reply.getKeyword());
            node.setExtra(map);
            nodeList.add(node);
        }
        return TreeUtil.build(nodeList, "0");
    }

    ChatAdmin loadAdminFromDB(String id) {
        SysUser sysUser = sysUserMapper.selectById(id);
        ChatAdmin admin = null;
        if (sysUser != null) {
            admin = new ChatAdmin();
            admin.setAdminId(id);
            admin.setTenantId(sysUser.getTenantId());
            admin.setPollId(sysUser.getUserId());
            admin.setAdminName(sysUser.getUserName());
            admin.setAdminNick(sysUser.getUserNick());
            admin.setMaxAccept(sysUser.getMaxAccept());
            List<SysGroup> groupList = sysGroupMapper.getByUserIdAndGroupType(id, 0);
            for (SysGroup group : groupList) {
                admin.getGroupId().add(group.getGroupId());
                admin.getGroupName().add(group.getGroupName());
            }
        }
        return admin;
    }

    public Set<ChatAdmin> getAdminSet(String tenantId, String groupId) {
        Set<String> adminIds = chatCacheService.getAdminSet(tenantId);
        Set<ChatAdmin> adminSet = new HashSet<>();
        for (String adminId : adminIds) {
            ChatAdmin admin = chatCacheService.getAdmin(adminId);
            //容错处理：如果客服已经离线，移除集合
            if (admin == null || admin.getStatus() == ChatConstant.admin_status_offline) {
                chatCacheService.delAdminSet(tenantId, adminId);
                continue;
            }
            //如果技能组id有值，根据技能组筛选
            if (StrUtil.isNotBlank(groupId) && !admin.getGroupId().contains(groupId)) {
                continue;
            }
            //选择所有登录的客服，包括在线和忙碌状态
            adminSet.add(admin);
        }
        return adminSet;
    }

     
    public void recallMsg(AdminRecallMsgP param) {
        ChatUser user = chatCacheService.getUser(param.getUserId());
        logger.info("admin_recallMsg:{},{}", KefuContext.getKefuContext(), param);
        if (user == null) {
            return;
        }
        UserGetRecallMsg userGetRecallMsg = new UserGetRecallMsg();
        userGetRecallMsg.setMsgId(param.getMsgId());
        chatCacheService.sendEventMsg(user.getPollId(), userGetRecallMsg);
        ChatSessionMsg chatSessionMsg = new ChatSessionMsg();
        chatSessionMsg.setId(param.getMsgId());
        chatSessionMsg.setRecallFlag(1);
        chatSessionMsgService.update(chatSessionMsg);
    }

     
    public void awakeWaitList(ChatAdmin admin) {
        if (admin.getStatus() != ChatConstant.admin_status_online) {
            return;
        }
        logger.info("awakeWaitList:{}", admin);
        //唤醒排队上锁，避免超过接待上线
        if (redisLockUtil.beLock("awakeWaitList:"+admin.getAdminId())) {
            logger.info("awakeWaitList_tooMany:{}", admin);
            return;
        }
        long maxAccept = admin.getMaxAccept();
        long nowAccept = chatCacheService.getAdminUserSetSize(admin.getAdminId());
        List<String> userIdList = chatCacheService.getWaitList(admin.getTenantId());
        logger.info("awakeWaitList:{},{},{}", admin, nowAccept, userIdList);
        for (String userId : userIdList) {
            if (nowAccept >= maxAccept) {
                break;
            }
            ChatUser user = chatCacheService.getUser(userId);
            String groupId = user.getGroupId();
            //如果排队用户有技能组，根据技能组筛选
            if (StrUtil.isNotBlank(groupId) && !admin.getGroupId().contains(groupId)) {
                continue;
            }
            boolean connectFlag = chatSessionService.chatConnect(user, admin, ChatConstant.connect_type_wait);
            if (connectFlag) {
                nowAccept++;
            }
        }
    }

    public List<ChatUserParam.UserGetSessionMsgR> getHistoryMsg(AdminGetHistoryMsgP param) {
        List<ChatUserParam.UserGetSessionMsgR> result = new ArrayList<>();
        List<ChatSessionMsg> msgList;
        //如果最大时间为0，查询当前会话纪录
        if (param.getMaxTime() == 0) {
            msgList = chatSessionMsgService.getSessionMsg(param.getSessionId());
            //逆序，改成按照消息时间，从大到小排序
            Collections.reverse(msgList);
            if (msgList.size() < param.getPageSize()) {
                Integer chatMsgFillFlag = sysTenantMapper.selectById(KefuContext.getTid()).getChatMsgFillFlag();
                //如果当前会话消息少于查询条数，且特殊企业设置了历史记录填充
                if (Objects.equals(chatMsgFillFlag,1)) {
                    long fillTime = CollUtil.getLast(msgList).getMsgTime();
                    int fillSize = param.getPageSize() - msgList.size();
                    List<ChatSessionMsg> historyMsg = chatSessionMsgService.getHistorySessionMsg(param.getUserId(), fillTime, fillSize);
                    CollUtil.addAll(msgList, historyMsg);
                }
            }
        } else {
            //如果最大时间不为0，根据最大时间往前查询
            msgList = chatSessionMsgService.getHistorySessionMsg(param.getUserId(), param.getMaxTime(), param.getPageSize());
        }
        for (ChatSessionMsg sessionMsg : msgList) {
            ChatUserParam.UserGetSessionMsgR msg = BeanUtil.toBean(sessionMsg, ChatUserParam.UserGetSessionMsgR.class);
            String msgContent = msg.getMsgContent();
            String docId = msg.getDocId();
            //移动端，机器人词条富文本处理
            if (param.getCleanHtmlTag() == 1 && StrUtil.isNotBlank(docId) && StrUtil.isNotBlank(msgContent)) {
                msg.setMsgContent(HtmlUtil.cleanHtmlTag(msgContent));
                if (msgContent.contains("http")) {
                    msg.setHtmlUrl(kefuProperties.getDomain()+"/chat_online/question?docId=" + msg.getDocId() + "&userId=" + param.getUserId());
                }
            }
            result.add(msg);
        }
        return result;
    }

    public void acceptVideo(AdminUserP param) {
        ChatEventMsg.UserVideoAcceptance msg = new ChatEventMsg.UserVideoAcceptance();
        ChatUser user = chatCacheService.getUser(param.getUserId());
        ChatAdmin admin = chatCacheService.getAdmin(user.getAdminId());
        msg.setAminId(KefuContext.getKefuContext().getUserId());
        logger.info("admin_acceptUserVideo:{},{},{}", param, admin,user);
         chatCacheService.sendEventMsg(user.getPollId(),msg);
    }

    public void refuseVideo(AdminUserP param) {
        ChatEventMsg.UserRejectCall msg = new ChatEventMsg.UserRejectCall();
        ChatUser user = chatCacheService.getUser(param.getUserId());
        ChatAdmin admin = chatCacheService.getAdmin(user.getAdminId());
        logger.info("admin_acceptUserAudio:{},{},{}", param, admin,user);
        chatCacheService.sendEventMsg(user.getPollId(),msg);
    }

    public GetTokenResult getAdminVideoToken() {
        HashMap<String, Object> map = new HashMap<>();
        KefuContext context = KefuContext.getKefuContext();
        if (redisTemplate.hasKey(ChatConstant.chat_rongCloud_key+context.getUserId())){
            String adminResult = redisTemplate.opsForValue().get(ChatConstant.chat_rongCloud_key+context.getUserId());
            return JacksonUtil.toBean(adminResult, GetTokenResult.class);
        }
        map.put("userId",context.getUserId());
        map.put("name",context.getUserName());
        GetTokenResult result = RongCloudUtils.requestCloud(map);
        if (result!=null&&result.getCode().equals(200)){
            redisTemplate.opsForValue().set(ChatConstant.chat_rongCloud_key+context.getUserId(), Objects.requireNonNull(JacksonUtil.toString(result)));
            redisTemplate.expire(ChatConstant.chat_rongCloud_key+context.getUserId(),24,TimeUnit.HOURS);
        }
        return  result;
    }


    public Boolean rongYunExpireToken(ChatRongYunExpireTokenVo vo) {
        //获取需要销毁的token
        String userId = vo.getUserId();
        if (userId.isEmpty()){
            throw new RuntimeException("id不能为空");
        }
        HashMap<String, Object > map  = new HashMap<String, Object>(){{
            put("userId",userId);
            put("time", String.valueOf(Calendar.getInstance().getTimeInMillis()));
        }};
        GetTokenResult result = new RongCloudUtils().requestExpireToken(map);
        if (result!=null){
            //删除redis中存放的数据
            redisTemplate.delete(ChatConstant.chat_rongCloud_key + userId);
            return true;
        }
        return false;
    }

    public String sysMsg(ChatAdminParam.AdminSysMsgP param) {
        String msgId = IdUtil.simpleUUID();
        ThreadUtil.execute(() -> {
            ChatUser user = chatCacheService.getUser(param.getUserId());
            if(user==null){
                return;
            }
            String sessionId = param.getSessionId();
            long msgContentCount = chatSessionMsgService.lambdaQuery().eq(ChatSessionMsg::getMsgContent, param.getMsgContent())
                    .eq(ChatSessionMsg::getSessionId, sessionId).eq(ChatSessionMsg::getSenderType,4).count();
            if(msgContentCount>0){
                return;
            }
            ChatSessionMsg chatSessionMsg = ChatSessionMsg.builder()
                    .senderType(4).msgType(1).msgContent(param.getMsgContent()).clientType(param.getClientType()).readFlag(1)
                    .id(msgId).sessionId(sessionId).userId(user.getUserId()).adminId(user.getAdminId())
                    .tenantId(user.getTenantId()).msgTime(System.currentTimeMillis()).build();
            chatSessionMsgService.save(chatSessionMsg);
            logger.info("访客端存储系统消息:{},{}",param, chatSessionMsg);
        });
        return msgId;
    }
}
